package jp.eisbahn.mgapi.auth;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import jp.eisbahn.mgapi.http.ContentType;
import jp.eisbahn.mgapi.http.HttpFetcher;
import jp.eisbahn.mgapi.http.HttpMethod;
import jp.eisbahn.mgapi.http.HttpRequest;
import jp.eisbahn.mgapi.http.HttpResponse;

import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

/**
 * This class has an ability to issue an access token.
 * @author Yoichiro Tanaka
 */
public class TokenProducer {

    /** The endpoint URL to issue the token. */
    private static final String MIXI_TOKEN_URL =
        "https://secure.mixi-platform.com/2/token";

    /** The configuration class. */
    private ClientConfig clientConfig;

    /** Http fetcher implementation object. */
    private HttpFetcher httpFetcher;

    /**
     * Set the configuration object.
     * @param newClientConfig The object which has
     * the information about the configuration.
     */
    public final void setClientConfig(final ClientConfig newClientConfig) {
        clientConfig = newClientConfig;
    }

    /**
     * Retrieve the configuration object.
     * @return The configuration object.
     */
    public final ClientConfig getClientConfig() {
        return clientConfig;
    }

    /**
     * Issue the access token.
     * @param authorizationCode The authorization code string.
     * @return The issued token.
     * @throws IOException If some errors occurred while accessing the endpoint.
     */
    public final Token issue(final String authorizationCode)
            throws IOException {
        Map<String, Object> parameterMap = new HashMap<String, Object>();
        parameterMap.put("grant_type", "authorization_code");
        parameterMap.put("client_id", clientConfig.getClientId());
        parameterMap.put("client_secret", clientConfig.getClientSecret());
        parameterMap.put("code", authorizationCode);
        parameterMap.put("redirect_uri", clientConfig.getRedirectUri());
        return retrieveToken(parameterMap);
    }

    /**
     * Re-issue the access token with the refresh token.
     * @param token The token object which has the valid refresh token.
     * @return The new issued token.
     * @throws IOException If some errors occurred while accessing the endpoint.
     */
    public final Token refresh(final Token token) throws IOException {
        Map<String, Object> parameterMap = new HashMap<String, Object>();
        parameterMap.put("grant_type", "refresh_token");
        parameterMap.put("client_id", clientConfig.getClientId());
        parameterMap.put("client_secret", clientConfig.getClientSecret());
        parameterMap.put("refresh_token", token.getRefreshToken());
        return retrieveToken(parameterMap);
    }

    /**
     * Retrieve the token.
     * @param parameterMap The map object which has each parameter.
     * @return The token object.
     * @throws IOException If some errors occurred while accessing the endpoint.
     */
    private Token retrieveToken(final Map<String, Object> parameterMap)
            throws IOException {
        HttpRequest request = new HttpRequest(MIXI_TOKEN_URL, parameterMap,
                HttpMethod.POST, ContentType.X_WWW_FORM_URLENCODED);
        HttpResponse response = httpFetcher.fetch(request);
        if (response.getResponseCode() == HttpServletResponse.SC_OK) {
            String responseBody = response.getResponseBody();
            Object parsed = JSONValue.parse(responseBody);
            if (parsed instanceof JSONObject) {
                JSONObject obj = (JSONObject) parsed;
                return new Token(
                        (String) obj.get("refresh_token"),
                        (String) obj.get("access_token"),
                        (Long) obj.get("expires_in"));
            } else {
                throw new IllegalStateException("Unknown response format.");
            }
        } else {
            return new Token(response.getResponseCode(),
                    response.getResponseMessage());
        }
    }

    /**
     * Set the Http fetcher implementation object.
     * @param newHttpFetcher The object implemented HttpFetcher interface.
     */
    public final void setHttpFetcher(final HttpFetcher newHttpFetcher) {
        httpFetcher = newHttpFetcher;
    }

}
